iT邦幫忙

2021 iThome 鐵人賽

DAY 22
1

為了更理解Alpine initramfs的眉角,今天持續來看一下Alpine的mkinitfs套件裡面的 nlplug-findfs 這隻helper tool。

然而這邊有些先備知識需要說,在Linux的世界觀裡,為了因應動態插拔裝置的狀況,有整套相輔相成的框架/機制,分別是 Kobject uevent

簡單來說(我們不提Bus/Class/Device......等等深入的技術細節、以我們的需求只講概念就好),今天在kernel對應的init stage進行平台init、或著有裝置被動態放進系統時,driver在進行probe(),會建立出對應的kobject、掛上一顆Linux在開機時逐步打造出來的kobject樹狀結構之中。在裝置是被動態放進來時,就會發生像某個起床氣很重的巫妖王誕生時一樣,整個羅德隆森林都在呼喊他的名諱:阿薩 — — 咳咳錯棚,該裝置對應的kobject建立完會順便呼叫 kobject_uevent_env ,將這個裝置誕生的喜訊通知整個世界,包含了userspace的hotplug管理程式。

在過去、或現代在編譯kernel時如果有設定 UEVENT_HELPER_PATH,亦或著打開 UEVENT_HELPER爾後再透過 echo 把管理程式fullpath寫到/sys/kernel/uevent_helper 中,那麼kernel就會在uevent產生時,事敝躬親地去fork、exec該管理程式,並且把裝置名稱、路徑、各式大小資訊排在環境變數中、交給hotplug管理程式自己決定要怎麼建立 /dev/ 底下的char/block dev;而且同時也會在sysfs底下建立一份「留存」。

話說到這裡,有個雞蛋問題,在kernel剛剛累死人地把第一支 process (initramfs時的/init)拉起來時,裝置的kobject數早就建完了,可是/dev/底下還是空的、因為當時不會有人可以接uevent(事實上kernel init在建tree時也不會打),那麼、要怎麼建出 /dev 底下那票device file呢?

答案是— —手動戳 sysfs底下的裝置的 uevent,然後他就會打了uevent上來給人接了。

長篇大論講這麼多,其實就是要帶出 nlplug-findfs 的目的,一如前幾偏一直強調的,Alpine的rootfs在一般預設下,是boot time去抓alpine_repo指向的地方、透過套件管理系統一個一個裝套件裝出來的,如果是走網路、那就歸另外一條,但是如果今天Alpine Init需要的東西、或著alpine_repo指向block dev呢?以一些distro的設計哲學,可能會選擇一條道路,那就是直接用 busyboxmdev直接下一道 mdev -s,此時mdev就會去遍歷 /sys 、一個一個把 uevent 戳起來,全部按照udev rules拉到 /dev 底下,再看要怎麼處理。

但是Alpine的設計理念是不做多餘的事情、也不要建立不必要的檔案,最小、最輕量化。Alpine Init呼叫的方式為: nlplug-findfs -p /sbin/mdev -d -n -a /tmp/apkovls

簡單意義是,開始去/sys底下,有智慧性地找device node,然後戳它、叫他噴ADD uevent上來,但要拿 /sbin/mdev 來幫我接 uevent (其實 nlplug-findfs 自己有一些handle_uevent的邏輯、不過最小effort嘛,有busybox那就叫它作就好)

static void trigger_uevent_cb(struct recurse_opts *opts, void *data)
{
	size_t oldlen;
	int fd;

	if (!recurse_push(opts, &oldlen, "uevent"))
		return;

	fd = open(opts->path, O_WRONLY | O_CLOEXEC);
	if (fd >= 0) {
		write(fd, "add", 3);
		close(fd);
	}
	recurse_pop(opts, oldlen);
}

在茫茫kobject中這樣不斷的戳uevent起來ADDD,直到幫我找到某個有檔案系統的地方,含有apkovls.tar.gz這個檔案

static void scandev_cb(struct recurse_opts *opts, void *data)
{
	struct scandevctx *ctx = data;
	struct ueventconf *conf = ctx->conf;

	if (opts->is_dir) {
		size_t oldlen;
		int ok = 0;
		if (recurse_push(opts, &oldlen, ".boot_repository")) {
			ok = access(opts->path, F_OK) == 0;
			recurse_pop(opts, oldlen);
		}
		if (ok) {
			dbg("added boot repository %s to %s", opts->path, conf->bootrepos);
			append_line(conf->bootrepos, opts->path);
			ctx->found |= FOUND_BOOTREPO;
		}
	} else if (fnmatch("*.apkovl.tar.gz*", opts->filename, 0) == 0) {
		dbg("found apkovl %s", opts->path);
		append_line(conf->apkovls, opts->path);
		ctx->found |= FOUND_APKOVL;
	}
}

然後把他mount好、並且把檔案系統的path寫到 /tmp/apkovls 之中,而那個apkovl.tar.gz,會帶有repo套件庫、跟必要的檔案,以供後續讓Alpine Init拼出rootfs。

下一篇,我們應該會正式進入繼續手把手組出可以用的開機流程路上~


上一篇
Alpine Linux Porting (一點九?)
下一篇
Alpine Linux Porting (1.11?)
系列文
Port Alpine Linux to open source RISC-V platform30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言